#! /usr/bin/env python3.8
# -*- coding:utf-8 -*-


import logging
import os
import re
import threading
import time
import sys
from logging import handlers

import requests
from collections import deque
from requests import ReadTimeout
import random


class Logger(object):
    level_relations = {
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR,
        'crit': logging.CRITICAL
    }  # 日志级别关系映射

    def __init__(self, filename, level='info', when='D', backcount=1):
        # logging.basicConfig(stream=sys.stdout, level=logging.INFO)
        fmt = "%(asctime)s - %(levelname)s: %(message)s"
        self.logger = logging.getLogger(filename)
        format_str = logging.Formatter(fmt)  # 设置日志格式
        self.logger.setLevel(self.level_relations.get(level))  # 设置日志级别
        sh = logging.StreamHandler()  # 往屏幕上输出
        sh.setFormatter(format_str)  # 设置屏幕上显示的格式
        sh.stream = sys.stderr
        th = handlers.TimedRotatingFileHandler(filename=filename, when=when,
                                               encoding='utf-8')  # 往文件里写入#指定间隔时间自动生成文件的处理器
        # 实例化TimedRotatingFileHandler
        # interval是时间间隔，backupCount是备份文件的个数，如果超过这个个数，就会自动删除，when是间隔的时间单位，单位有以下几种：
        # S 秒
        # M 分
        # H 小时、
        # D 天、
        # W 每星期（interval==0时代表星期一）
        # midnight 每天凌晨
        th.setFormatter(format_str)  # 设置文件里写入的格式
        self.logger.addHandler(sh)  # 把对象加到logger里
        self.logger.addHandler(th)


class Res_Cdd(object):

    def __init__(self, data=None):
        self.post_data = data
        self.interval_time = 0.1  # 全局延迟时间间隔
        self.dq = deque()
        self.lock = threading.RLock()
        self.stopped = False
        if not os.path.exists("log"):
            os.mkdir("log")
        self.log = Logger('log/{}.log'.format(os.path.basename(__file__)), level='debug')
        self.post_url = "https://xjdwsy.cn/reg/post2"
        self.user_agents=["Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1",
                          "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0",
                          "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
                          "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.9.168 Version/11.50",
                          "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36",
                          "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; Tablet PC 2.0; .NET4.0E)",
                          "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3)",
                          "Mozilla/5.0 (Windows; U; Windows NT 6.1; ) AppleWebKit/534.12 (KHTML, like Gecko) Maxthon/3.0 Safari/534.12",
                          "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)",
                          "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E; SE 2.X MetaSr 1.0)",
                          "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.33 Safari/534.3 SE 2.X MetaSr 1.0",
                          "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)",
                          "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1 QQBrowser/6.9.11079.201",
                          "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E) QQBrowser/6.9.11079.201",
                          "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)",
                          "Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5",
                          "Mozilla/5.0 (Linux; U; Android 2.2.1; zh-cn; HTC_Wildfire_A3333 Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
                          "Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
                          "MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1",
                          "Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10",
                          "UCWEB7.0.2.37/28/999"]

    def get_capctha(self, url, session):
        """ 获取验证码 """
        im = session.get(url).content
        filename = "captcha.png"
        with open(filename, 'wb+') as f:
            f.write(im)

        inp = input("输入验证码:")
        if len(inp) != 4:
            return self.get_capctha(url, session)
        else:
            return inp

    def trace_func(func):
        def wrapper(self, *args, **kwargs):
            self.appear("{} start.".format(func.__name__))
            return func(self, *args, **kwargs)

        return wrapper

    def appear(self, msg):
        self.log.logger.info("{} {}".format(threading.currentThread().name, msg))

    @trace_func
    def get_token(self, url, proxy=None, session=None):
        """获取表单_token值"""
        pattern = re.compile("(?<=_token\" value=\").*(?=\">)")
        result = None

        while result is None:
            try:
                time.sleep(self.interval_time)
                html = session.get(url, proxies=proxy, timeout=15)  # 访问登记信息界面
                result = pattern.search(html.text)
            except Exception as e:
                self.appear("无法链接代理IP")
                time.sleep(1)
        _token = result.group(0)
        return _token

    def keep_alive(self, url, proxy=None, session=None, thread_num=None):
        """
        和服务器保持连接
        """
        session.headers['User-Agent'] = random.sample(self.user_agents, 1)[0]
        while True:  # 检测是否可以预定
            try:
                if self.stopped:
                    exit(0)
                html = session.get(url, proxies=proxy, timeout=15)  # 访问登记信息界面
                if html.status_code == 200:
                    print(thread_num, end=' ', flush=True)
                elif html.status_code == 503:
                    print(f"[{thread_num}]", end=' ', flush=True)
                time.sleep(1)
            except Exception as e:
                print("[x]", end=' ', flush=True)

    def get_proxy(self):
        """ 获取代理IP """
        self.lock.acquire()
        if self.dq.__len__() < 1:
            proxy_api = "http://tiqu.linksocket.com:81/abroad?num=10&type=1&lb=4&sb=0&flow=1&regions=china&n=1"  # Ipidea全球代理
            resp = requests.get(proxy_api).text.split('\n')[:-1]
            for x in resp:
                self.dq.append({"http": 'socks5://{}'.format(x),
                                "https": 'socks5://{}'.format(x)})
        self.lock.release()
        ip = self.dq.popleft()
        self.appear(ip)
        return ip

    def checkIdcard(self,idcard):
        if len(idcard) == 18:
            resp = requests.post(url="http://api.ip33.com/Area_Code/GetArea",data={"code": idcard[:6]})
            return resp.json().get("province") != ''
        else:
            return False
    def post(self, session=None, proxy=None, data=None , interval_time=0):
        try:
            self.stopped = True             #停止keep_alive
            time.sleep(interval_time)
            self.appear("start post.")
            html = session.post(self.post_url, data=data, proxies=proxy, timeout=15)  # 提交数据
            print(html.status_code)
            if re.search("预约凭证", html.text):
                self.appear("{}".format(re.search(r"预约顺序号:\s+\d+", html.text).group(0)))
                self.appear("姓名: {} ".format(data['patient_name']))
                self.appear("身份证: {}".format(data['patient_idcard']))
                self.appear("查询链接: https://xjdwsy.cn/reg/queryRegByIdcard?idcard={}&doctor_id=1019".format(
                    data['patient_idcard']))
            elif html.status_code == 503:
                print('[x] ', end=' ', flush=True)
            elif re.search('(?<=text-center">).*(?=</h3>)', html.text):
                self.appear(re.search('(?<=text-center">).*(?=</h3>)', html.text).group(0))
            elif re.search("验证码错误", html.text):
                self.appear("验证码错误！")
            else:
                self.appear("未知错误！")
                self.appear(html.content.decode('utf-8'))
        except ReadTimeout:
            self.appear("等待时间超时。")
        time.sleep(60)
        os._exit(0)

    @trace_func
    def main(self, data=None, session=requests.session(), interval_time=0):
        if self.checkIdcard(data['patient_idcard']) == False:
            self.appear("身份证错误！！")
            os._exit(1)
        thread_num = threading.currentThread().name.split('-')[-1]
        proxy = None
        session.headers = {
            "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
            "DNT": "1",
            "Host": "xjdwsy.cn",
            "Pragma": "no-cache",
            "Referer": "https://xjdwsy.cn/reg/post2?doctor_id=1019",
            "Accept-Encoding": "gzip, deflate, br",
            "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "User-Agent": random.sample(self.user_agents, 1)[0]
        }
        if data.get('doctor_id') is None:
            data['doctor_id'] = '1019'
        token_url = "https://xjdwsy.cn/admin/login"

        data["_token"] = self.get_token(token_url, proxy=proxy, session=session)
        self.appear(data)

        # k = threading.Thread(target=self.keep_alive, args=(self.post_url, proxy, session, thread_num))
        # k.start()


        from apscheduler.schedulers.blocking import BlockingScheduler
        scheduler = BlockingScheduler()
        scheduler.add_job(self.post, 'cron', hour=6, minute=0, second=0, args=[session, proxy, data, interval_time])
        scheduler.start()

    @trace_func
    def threads(self):
        """ 每个人一个线程 """
        thread_list = []
        interv_time = 0
        if len(self.post_data) <= 3:
            time_step = 0.05
        elif len(self.post_data) == 4:
            time_step = 0.2
        for d in self.post_data:
            session = requests.session()
            captcha = self.get_capctha("https://xjdwsy.cn/captcha/default?fhd8SbuP", session)
            d['captcha'] = captcha
            t = threading.Thread(target=self.main, args=(d, session,interv_time,))
            interv_time += time_step        #3个人0.1,4个人0.2,一般不超过3个人
            thread_list.append(t)
        for t in thread_list:
            t.start()

if __name__ == "__main__":
    data = [
            {
            "patient_name": "",
            "patient_sex": "",
            "patient_idcard": "",
            "patient_phone": ""},
    ]
    res = Res_Cdd(data)
    res.threads()
